﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using HeritageAcademy.Models.Loaders;
using HeritageAcademy.Shared;
using HeritageAcademy.Shared.Properties;

namespace HeritageAcademy.Models.DataModels
{
    [XmlRoot(ElementName = "book-extension", Namespace = "books:shared")]
    public class Course : ModelBase
    {
        #region Parameters
        
        private string _contributor;
        private string _coverage;
        private string _creator;
        private string _date;
        private string _description;
        private string _format;
        private string _genre;
        private string _identifier;
        private string _language;
        private int _paragraphCount;
        private string _publisher;
        private string _relation;
        private string _rights;
        private string _source;
        private string _subject;
        private string _title;
        private string _type;
        private CourseUserMetadata _userMetadata;
        private int? _yearOfPublication;
        private int _chapterDepth;
        private List<Chapter> _childChapters = new List<Chapter>();
        private List<Chapter> _flatChapters = new List<Chapter>();
        private string _folder;
        private bool _imagesExtracted;
        private string _ncxHref = String.Empty;
        
        #endregion

        #region Properties
        
        private string BookFileName
        {
            get
            {
                var fileName = String.Format(Resources.Course_SerializedObjectPathFormat, Folder, FolderName);
                return fileName;
            }
        }

        public int ChapterDepth
        {
            get { return _chapterDepth; }
            set
            {
                _chapterDepth = value;
                RaisePropertyChanged("ChapterDepth");
            }
        }

        public List<Chapter> Chapters
        {
            get { return _childChapters; }
            set
            {
                _childChapters = value;
                RaisePropertyChanged("Chapters");
            }
        }

        internal List<Chapter> FlatChapters
        {
            get { return _flatChapters; }
        }

        public string Contributor
        {
            get { return _contributor; }
            set
            {
                _contributor = value;
                RaisePropertyChanged("Contributor");
            }
        }

        public string Coverage
        {
            get { return _coverage; }
            set
            {
                _coverage = value;
                RaisePropertyChanged("Coverage");
            }
        }

        public string Creator
        {
            get { return _creator; }
            set
            {
                _creator = value;
                RaisePropertyChanged("Creator");
            }
        }

        public string Date
        {
            get { return _date; }
            set
            {
                _date = value;
                RaisePropertyChanged("Date");
            }
        }

        public string Description
        {
            get { return _description; }
            set
            {
                _description = value;
                RaisePropertyChanged("Description");
            }
        }

        public string Folder
        {
            get { return _folder; }
            set
            {
                _folder = value;
                foreach (var ch in _flatChapters)
                {
                    ch.FolderName = FolderName;
                }
                RaisePropertyChanged("Folder");
            }
        }

        public string FolderName
        {
            get { return GetValidFolderName(Title); }
        }

        [XmlIgnore]
        public string ZipPath { get; set; }

        public string Format
        {
            get { return _format; }
            set
            {
                _format = value;
                RaisePropertyChanged("Format");
            }
        }

        public string Genre
        {
            get { return _genre ?? "Unknown"; }
            set
            {
                _genre = value;
                RaisePropertyChanged("Genre");
            }
        }

        public string Identifier
        {
            get { return _identifier; }
            set
            {
                _identifier = value;
                RaisePropertyChanged("Identifier");
            }
        }

        public string Language
        {
            get { return _language; }
            set
            {
                _language = value;
                RaisePropertyChanged("Language");
            }
        }

        public int ParagraphCount
        {
            get { return _paragraphCount; }
            set
            {
                _paragraphCount = value;
                RaisePropertyChanged("ParagraphCount");
            }
        }

        public string Publisher
        {
            get { return _publisher; }
            set
            {
                _publisher = value;
                RaisePropertyChanged("Publisher");
            }
        }

        public string Relation
        {
            get { return _relation; }
            set
            {
                _relation = value;
                RaisePropertyChanged("Relation");
            }
        }

        public string Rights
        {
            get { return _rights; }
            set
            {
                _rights = value;
                RaisePropertyChanged("Rights");
            }
        }

        public string Source
        {
            get { return _source; }
            set
            {
                _source = value;
                RaisePropertyChanged("Source");
            }
        }

        public string Subject
        {
            get { return _subject; }
            set
            {
                _subject = value;
                RaisePropertyChanged("Subject");
            }
        }

        public string Title
        {
            get { return _title; }
            set
            {
                _title = value;
                RaisePropertyChanged("Title");
            }
        }

        public string Type
        {
            get { return _type; }
            set
            {
                _type = value;
                RaisePropertyChanged("Type");
            }
        }

        [XmlIgnore]
        public CourseUserMetadata UserMetadata
        {
            get
            {
                if (_userMetadata == null)
                {
                    var filename = string.Format(Resources.Course_UserMetadataFilename,
                                                 Path.GetFileNameWithoutExtension(ZipPath));
                    var fullPath = Path.Combine(IOHelper.CourseUserMetadataFolder, filename);
                    _userMetadata = CourseUserMetadata.GetFromDisk(fullPath);
                }
                return _userMetadata;
            }
            set
            {
                _userMetadata = value;
                RaisePropertyChanged("UserMetadata");
            }
        }

        public int? YearOfPublication
        {
            get { return _yearOfPublication; }
            set
            {
                _yearOfPublication = value;
                RaisePropertyChanged("YearOfPublication");
            }
        }

        internal void SortFlatChapters()
        {
            _flatChapters = _flatChapters.OrderBy(p => p.PlayOrder).ToList();
        }
        
        #endregion Properties

        #region Public Methods
        
        public string GetAllChaptersContent()
        {
            var allChaptersContent = String.Empty;
            for (var i = 0; i < _flatChapters.Count; i++)
            {
                var directoryPath = String.Format(Resources.Course_DirectoryPathFormat,
                                                     AppDomain.CurrentDomain.BaseDirectory, FolderName);
                var imagesPath = Path.Combine(IOHelper.CourseTemporaryFolder, FolderName);

                TextReader tr =
                    new StreamReader(String.Format(Resources.Course_ChapterFilesPathFormat, directoryPath,
                                                   (i + 1).ToString(Resources.Course_ChapterNumberFormat)));
                var content = tr.ReadToEnd();
                content = content.Replace(Resources.Course_CoursePathTag, imagesPath);
                tr.Close();
                var xmlSection = new XmlDocument();
                xmlSection.LoadXml(content);
                allChaptersContent += xmlSection.GetElementsByTagName("Section")[0].OuterXml;
            }

            if (!_imagesExtracted)
            {
                _imagesExtracted = true;
                CourseLoader.ExtractImages(this);
            }

            return String.Format(Resources.Course_FlowDocumentParentFormat, allChaptersContent);
        }

        /// <summary>
        /// Get chapter content as formated string.
        /// </summary>
        /// <param name="pChapterIndex">Chapter index</param>
        /// <returns>Formated string</returns>
        public string GetChapterContent(int pChapterIndex)
        {
            var chaptersContent = String.Empty;

            var imagesPath = Path.Combine(IOHelper.CourseTemporaryFolder, FolderName);
            var srm = CourseLoader.GetChapterStreamFromZip(this, pChapterIndex);

            TextReader tr = new StreamReader(srm);

            var content = tr.ReadToEnd();
            content = content.Replace(Resources.Course_CoursePathTag, imagesPath);
            tr.Close();
            var xmlSection = new XmlDocument();
            xmlSection.LoadXml(content);
            chaptersContent += xmlSection.GetElementsByTagName(Resources.Course_ChapterTagNameName)[0].OuterXml;

            if (!_imagesExtracted)
            {
                _imagesExtracted = true;
                CourseLoader.ExtractImages(this);
            }
            CourseLoader.ExtractTests(this);

            return String.Format(Resources.Course_FlowDocumentParentFormat, chaptersContent);
        }

        public string GetChapterFileName(int pChapterIndex)
        {
            var directoryPath = Folder;
            var format = Resources.Course_ChapterFilesPathFormat;
            var numFormat = Resources.Course_ChapterNumberFormat;
            var chNumber = (pChapterIndex + 1).ToString(numFormat);
            var filePath = String.Format(format, directoryPath, chNumber);
            return filePath;
        }

        public string GetChapterNotesFileName(int pChapterIndex)
        {
            var filename = string.Format(Resources.Course_ChapterNotesFilesPathFormat,
                                        Path.GetFileNameWithoutExtension(ZipPath),
                                        (pChapterIndex + 1).ToString(Resources.Course_ChapterNumberFormat));
            var fullPath = Path.Combine(IOHelper.CourseUserMetadataFolder, filename);
            return fullPath;
        }
        
        #endregion Public Methods

        #region Private Methods
        
        internal void AddChapter(Chapter newChapter, Chapter parentChapter)
        {
            newChapter.FolderName = FolderName;

            if (parentChapter == null)
                Chapters.Add(newChapter);
            else
                parentChapter.Chapters.Add(newChapter);

            _flatChapters.Add(newChapter);
        }

        public string GetValidFolderName(string pTitle)
        {
            var folderNameAux = pTitle;
            var invalidChars = Path.GetInvalidFileNameChars();

            var aux = folderNameAux;
            folderNameAux = invalidChars.Where(c => aux.Contains(c.ToString())).Aggregate(folderNameAux, (current, c) => current.Replace(c.ToString(), String.Empty));

            const int limit = 60;
            if (folderNameAux.Length > limit)
                folderNameAux = WordCut(folderNameAux, limit, Resources.Course_WordCutSeparators.ToCharArray());

            return folderNameAux;
        }

        private string WordCut(string text, int cutOffLength, char[] separators)
        {
            cutOffLength = cutOffLength > text.Length ? text.Length : cutOffLength;
            var separatorIndex = text.Substring(0, cutOffLength).LastIndexOfAny(separators);
            return text.Substring(0, separatorIndex > 0 ? separatorIndex : cutOffLength);
        }
        
        #endregion Private Methods
    }
}